using System;
using NEnsure.Builders;
using System.Collections.Generic;

namespace NEnsure.Containers
{
	public class RuleContainer<T> : IRuleContainer<T>
	{
		private readonly string _propertyName;
		private readonly T _propertyValue;
		private readonly List<IBusinessRule> _rules;
		private readonly BrokenRuleList _brokenRules;
		private readonly IBrokenRuleBuilder _builder;

		public string PropertyName
		{
			get { return _propertyName; }
		}

		public T PropertyValue
		{
			get { return _propertyValue; }
		}

		public BrokenRule[] BrokenRules
		{
			get { return _brokenRules.ToArray(); }
		}

		public IBusinessRule[] Rules
		{
			get { return _rules.ToArray(); }
		}

		public event ValidationHandler<EventArgs> RanRule;

		public event ValidationHandler<EventArgs> RunningRule;

		public RuleContainer(string propertyName, T propertyValue)
			: this(propertyName, propertyValue, new DefaultBrokenRuleBuilder())
		{
			
		}

		public RuleContainer(string propertyName, T propertyValue, IBrokenRuleBuilder brokenRuleBuilder)
		{
			if (string.IsNullOrEmpty(propertyName))
			{
				throw new ArgumentNullException("propertyName");
			}

			if (brokenRuleBuilder == null)
			{
				throw new ArgumentNullException("brokenRuleBuilder");
			}

			_propertyName = propertyName;
			_propertyValue = propertyValue;
			_rules = new List<IBusinessRule>();
			_brokenRules = new BrokenRuleList();
			_builder = brokenRuleBuilder;
		}

		public IRuleContainer<T> Satisfies(params IBusinessRule[] rules)
		{
			foreach(IBusinessRule rule in rules)
			{
				if (rule == null)
				{
					continue;	
				}

				// TODO Do we want to look at the rule key?
				if (_rules.Contains(rule))
				{
					continue;
				}

				if ((rule as IBusinessRule<T>) == null)
				{
					throw new InvalidOperationException(
						"All business rules in a container must operate on the same value type. " + 
						"For example, if one business rule operates on a string, another business " + 
						"rule in the same container can't operate on an int. This is because the same " + 
						"property can't both be a string and an int at the same time.");
				}

				_rules.Add(rule);	
			}

			return this;
		}

		public bool AreAllRulesSatisfied()
		{
			foreach(IBusinessRule<T> rule in _rules)
			{
				if (RunningRule != null)
				{
					RunningRule(this, EventArgs.Empty);
				}

				if (rule.IsBrokenBy(_propertyValue))
				{
					_brokenRules.Add(_builder.Build(rule, _propertyName, _propertyValue));
				}

				if (RanRule != null)
				{
					RanRule(this, EventArgs.Empty);
				}
			}

			return _brokenRules.Count == 0;
		}
	}
}
